package com.agileboot.domain.medicine.db;

import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.medicine.dto.MedicineDTO;
import com.agileboot.domain.medicine.query.MedicineField;
import com.agileboot.domain.medicine.query.MedicineSearchQuery;
import com.agileboot.domain.medicine.query.MyMedicineQuery;
import com.agileboot.domain.medicine.vo.MedicineInfoVO;
import com.agileboot.domain.search.db.SearchHistory;
import com.agileboot.domain.search.db.SearchHistoryMapper;
import com.agileboot.domain.search.db.SearchHistoryService;
import com.agileboot.domain.store.db.StoreInfo;
import com.agileboot.domain.store.db.StoreInfoMapper;
import com.agileboot.domain.system.user.dto.UserDTO;
import com.agileboot.domain.system.user.dto.UserRanking;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.alibaba.fastjson.JSON;
import com.agileboot.domain.medicine.command.MedicineCommand;
import com.agileboot.domain.medicine.vo.MedicineVO;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * (Medicine)表服务实现类
 *
 * @author lx
 * @since 2023-12-02 14:20:00
 */
@Service
@Slf4j
public class MedicineServiceImpl extends ServiceImpl<MedicineMapper, Medicine> implements MedicineService {
    @Autowired
    private StoreInfoMapper storeInfoMapper;
    @Autowired
    private SearchHistoryMapper searchHistoryMapper;
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private SearchHistoryService searchHistoryService;

    @Override
    public Page<Medicine> getMedicineList(Page<Medicine> page, Wrapper<Medicine> queryWrapper) {
        return this.baseMapper.getMedicineList(page, queryWrapper);
    }

    @Override
    public boolean isMedicineNameDuplicated(String name) {
        QueryWrapper<Medicine> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", name);
        return this.baseMapper.exists(queryWrapper);
    }

    /**
     * 推荐模块
     *
     * @return
     */
    @Override
    public List<MedicineInfoVO> getSuggestMedicineList() {
        //检查用户是否登录
        Long userId = checkUserLogin();
        int all = 5;
        //查询该用户是否有过草药收藏
        Long storeMedicineNumber = getStoreMedicineNumberByUserId(userId, 2);
        HashSet<String> keys = new HashSet<>();
        //查询中草药推荐的关键字
        getMedicineQueryKeys(userId, all, storeMedicineNumber, keys);
        //组装关键字
        String esKey = String.join(",", keys);
        //Es-条件封装
        SearchRequest searchRequest = getSearchRequest(esKey);
        SearchResponse searchResponse = null;
        List<MedicineInfoVO> list = new ArrayList<>();
        try {
            //ES查询
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //获取查询结果
            SearchHits hits = searchResponse.getHits();
            //封装数据
            getESMedicineList(hits, list);
            // 关闭client
//            restHighLevelClient.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return list;
    }

    private void getMedicineQueryKeys(Long userId, int all, Long storeMedicineNumber, HashSet<String> keys) {
        if (storeMedicineNumber != null && storeMedicineNumber > 0) {
            //获取该用户的草药收藏
            if(storeMedicineNumber>3){
                getStoreMedicineList(userId, 3L, keys);
                all -= 3;
            }else{
                getStoreMedicineList(userId, storeMedicineNumber, keys);
                all -= storeMedicineNumber;
            }

        }
        Long number = getHistoryMedicineNumberByUserId(userId, all);
        if (number != null && number > 0) {
            getHirstoryMedicineList(userId, Math.toIntExact(all), keys);
            all -= number;
        }
        if (all > 0) {
            getMedicineList(all, keys);
        }
    }

    private void getStoreMedicineList(Long userId, Long storeMedicineNumber, HashSet<String> keys) {
        List<StoreInfo> storeInfoList = getStoreMedicineByUserId(userId, Math.toIntExact(storeMedicineNumber));
        Set<String> collect = storeInfoList.stream().map(StoreInfo::getMedicineName).collect(Collectors.toSet());
        if (!CollectionUtils.isEmpty(collect)) {
            keys.addAll(collect);
        }
    }

    private void getHirstoryMedicineList(Long userId, Integer number, HashSet<String> keys) {
        //获取该用户最近的关键字
        List<SearchHistory> searchHistoryList = getHistoryMedicineByUserId(userId, number);
        Set<String> collect = searchHistoryList.stream().map(SearchHistory::getSearchWord).distinct().collect(Collectors.toSet());
        if (!CollectionUtils.isEmpty(collect)) {
            keys.addAll(collect);
        }
    }

    private void getMedicineList(int all, HashSet<String> keys) {
        List<Medicine> medicines = baseMapper.selectList(Wrappers.lambdaQuery(Medicine.class).select(Medicine::getName).last("limit " + all));
        Set<String> collect = medicines.stream().map(Medicine::getName).collect(Collectors.toSet());
        if (!CollectionUtils.isEmpty(collect)) {
            keys.addAll(collect);
        }
    }

    private SearchRequest getSearchRequest(String esKey) {
        SearchRequest searchRequest = new SearchRequest("medicine");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.queryStringQuery(String.join(" ", esKey)).field("name"));
        searchSourceBuilder.query(QueryBuilders.queryStringQuery(String.join(" ", esKey)).field("address"));
//        searchSourceBuilder.query(QueryBuilders.queryStringQuery(String.join(" ", esKey)).field("shape"));
        searchSourceBuilder.query(QueryBuilders.queryStringQuery(String.join(" ", esKey)).field("taboo"));
//        searchSourceBuilder.query(QueryBuilders.queryStringQuery(String.join(" ", esKey)).field("flavorTropism"));
        searchSourceBuilder.query(QueryBuilders.queryStringQuery(String.join(" ", esKey)).field("gather"));
//        searchSourceBuilder.query(QueryBuilders.queryStringQuery(String.join(" ", esKey)).field("supplement"));
        searchSourceBuilder.from(1);
        searchSourceBuilder.size(5);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(searchSourceBuilder);
        return searchRequest;
    }

    private Long getStoreMedicineNumberByUserId(Long userId, int i) {
        LambdaQueryWrapper<StoreInfo> queryWrapper = getStoreInfoLambdaQueryWrapper(userId, i);
        return storeInfoMapper.selectCount(queryWrapper);
    }

    private Long getHistoryMedicineNumberByUserId(Long userId, int i) {
        LambdaQueryWrapper<SearchHistory> queryWrapper = getSearchHistoryLambdaQueryWrapper(userId, i);
        return searchHistoryMapper.selectCount(queryWrapper);
    }

    private Long checkUserLogin() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        SystemLoginUser systemLoginUser = (SystemLoginUser) authentication.getPrincipal();
        Assert.notNull(systemLoginUser, "当前没有用户登录");
        return systemLoginUser.getUserId();
    }

    private List<SearchHistory> getHistoryMedicineByUserId(Long userId, Integer num) {
        LambdaQueryWrapper<SearchHistory> queryWrapper = getSearchHistoryLambdaQueryWrapper(userId, num);
        return searchHistoryMapper.selectList(queryWrapper);
    }

    private LambdaQueryWrapper<SearchHistory> getSearchHistoryLambdaQueryWrapper(Long userId, Integer num) {
        LambdaQueryWrapper<SearchHistory> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(SearchHistory::getSearchWord);
        queryWrapper.eq(SearchHistory::getUserId, userId);
        queryWrapper.orderByDesc(SearchHistory::getCreateTime);
        queryWrapper.last("limit " + num);
        return queryWrapper;
    }

    //获取该用户的草药收藏
    private List<StoreInfo> getStoreMedicineByUserId(Long userId, Integer num) {
        LambdaQueryWrapper<StoreInfo> queryWrapper = getStoreInfoLambdaQueryWrapper(userId, num);
        return storeInfoMapper.selectList(queryWrapper);
    }

    private LambdaQueryWrapper<StoreInfo> getStoreInfoLambdaQueryWrapper(Long userId, Integer num) {
        LambdaQueryWrapper<StoreInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(StoreInfo::getMedicineName);
        queryWrapper.eq(StoreInfo::getUserId, userId);
        queryWrapper.orderByDesc(StoreInfo::getCreateTime);
        queryWrapper.last("limit " + num);
        return queryWrapper;
    }

    @Override
    public void addMedicine(List<MedicineCommand> commands) throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
        // 批量请求处理
        for (int i = 0; i < commands.size(); i++) {
            bulkRequest.add(
                    // 这里是数据信息
                    new IndexRequest("medicine")
                            .id("" + (i + 1))
                            .source(JSON.toJSONString(commands.get(i)), XContentType.JSON)
            );
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulk.status());// ok
    }

    /**
     * 1、没有任何查询条件，查询是类型默认是查全部，只进行分页查询
     * 2、只进行输入框查询，（需要做高亮查询，分词，还要做搜索记录）
     * 3、高级查询（需要返回字段和搜索字段进行精准查询）
     * 注意：在用户选用类型的时候，需要判断用户时候携带搜索字段
     *
     * @param medicineSearchQuery
     * @return
     * @throws IOException
     */
    @Override
    public MedicineVO searchMedicineList(MedicineSearchQuery medicineSearchQuery) throws IOException {
        // 1.创建查询请求对象
        SearchRequest searchRequest = new SearchRequest("medicine");
        // 2.构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //提取字段值
        String search = medicineSearchQuery.getSearch();
        //类型转换
//        String type = getTypeStr(medicineSearchQuery.getType());

        //分页数据
        Page<Medicine> page = medicineSearchQuery.toPage();
        // 分页
        searchSourceBuilder.from((int) ((page.getCurrent() - 1) * page.getSize()));
        searchSourceBuilder.size((int) page.getSize());
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        // (1)查询条件 使用QueryBuilders工具类创建
        // 制定检索条件
        BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
        //类型必须加
        if (medicineSearchQuery.getType()!=1) {
//            boolBuilder.must(QueryBuilders.matchPhraseQuery("type.keyword",type));
        boolBuilder.must(QueryBuilders.matchPhraseQuery("type.keyword", medicineSearchQuery.getType()));
        }
        //如果多字段列表不为空就相当于是进行了高级查询
        if (!CollectionUtils.isEmpty(medicineSearchQuery.getMedicineFields())) {
            //组装查询条件  ==》通过 精确查询进行组装
            boolBuilder = getBoolBuilder(medicineSearchQuery.getMedicineFields(), boolBuilder);
            // (3)条件投入
            searchSourceBuilder.query(boolBuilder);
        } else if (StringUtils.isNotBlank(search)) {
            //自定义高亮 查找
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.preTags("<font color='red'>");
            highlightBuilder.postTags("</font>");
            highlightBuilder.field("name");
            highlightBuilder.requireFieldMatch(false); //多字段时，需要设置为false
            highlightBuilder.field("flavorTropism");
            highlightBuilder.field("address");
            searchSourceBuilder.highlighter(highlightBuilder);
            // 匹配查询  ->(将搜索字段进行分词查询)
            boolBuilder.must(QueryBuilders.queryStringQuery(search));
//            QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(search);
            // (3)条件投入
            searchSourceBuilder.query(boolBuilder);

            //添加搜索历史记录
            if (page.getSize() != 3) {
                SearchHistory searchHistory = new SearchHistory();
                searchHistory.setUserId(AuthenticationUtils.getUserId());
                searchHistory.setSearchWord(search);
                searchHistoryService.save(searchHistory);
            }
        } else {
            // 分页查询
//            if (StringUtils.isBlank(type)) {
//                boolBuilder.must(QueryBuilders.matchAllQuery());
//            }
            // (3)条件投入
            searchSourceBuilder.query(boolBuilder);
        }
        // 3.添加条件到请求
        searchRequest.source(searchSourceBuilder);
        // 4.客户端查询请求
        SearchResponse result = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);


        // 5.查看返回结果
        SearchHits hits = result.getHits();
        long total = hits.getTotalHits().value;
        System.out.println(JSON.toJSONString(hits));
        System.out.println("=======================");
        ArrayList<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit documentFields : hits.getHits()) {
            //原字段
            Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
            //解析高亮字段
            Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
            HighlightField field1 = highlightFields.get("flavorTropism");
            HighlightField field2 = highlightFields.get("name");
            HighlightField field3 = highlightFields.get("address");
            fillFiledData(sourceAsMap, field1, "flavorTropism");
            fillFiledData(sourceAsMap, field2, "name");
            fillFiledData(sourceAsMap, field3, "address");
            sourceAsMap.put("id", documentFields.getId());
            list.add(documentFields.getSourceAsMap());
        }
        MedicineVO medicineVO = new MedicineVO();
        medicineVO.setData(list);
        medicineVO.setTotal(total);
        medicineVO.setPageNum(page.getCurrent());
        medicineVO.setPageSize(page.getSize());
        return medicineVO;

    }

    //替换原字段值为高亮字段值
    private void fillFiledData(Map<String, Object> sourceAsMap, HighlightField field, String filed) {
        if (field != null) {
            Text[] fragments = field.fragments();
            String flavorTropism = "";
            for (Text fragment : fragments) {
                flavorTropism += fragment;
            }
            sourceAsMap.put(filed, flavorTropism);
        }
    }

    //组装查询条件
    private BoolQueryBuilder getBoolBuilder(List<MedicineField> medicineFields, BoolQueryBuilder boolBuilder) {
        for (MedicineField medicineField : medicineFields) {
            boolBuilder.should(QueryBuilders.termQuery(medicineField.getField(), medicineField.getValue()));
        }
        return boolBuilder;
    }


    //将草药数字转换成草药类型
    private String getTypeStr(int type) {
        switch (type) {
            case 1:
                return "";
            case 2:
                return "植物类";
            case 3:
                return "动物类";
            case 4:
                return "矿物类";
            case 5:
                return "细菌类";
            default:
                return "";
        }
    }

    @Override
    public MedicineInfoVO getMedicineDetail(MedicineDTO medicineDTO) throws IOException {
        if (Objects.isNull(medicineDTO)) throw new ApiException(ErrorCode.Business.REQUEST_FIELD_EMPTY);
        if (StringUtils.isBlank(medicineDTO.getName())) throw new ApiException(ErrorCode.Business.REQUEST_FIELD_EMPTY);
        // 1.创建查询请求对象
        SearchRequest searchRequest = new SearchRequest("medicine");
        // 2.构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //Fuzzy 查找
        TermQueryBuilder termQuery = QueryBuilders.termQuery("name.keyword", medicineDTO.getName());
        searchSourceBuilder.query(termQuery);
        SearchRequest source = searchRequest.source(searchSourceBuilder);
        SearchResponse result = restHighLevelClient.search(source, RequestOptions.DEFAULT);
        // 5.查看返回结果
        SearchHits hits = result.getHits();
        List<MedicineInfoVO> list = new ArrayList<>();
        getESMedicineList(hits, list);
        if (CollectionUtils.isEmpty(list)) return new MedicineInfoVO();
        return list.get(0);
    }

    private void getESMedicineList(SearchHits hits, List<MedicineInfoVO> list) {
        for (SearchHit documentFields : hits.getHits()) {
            MedicineInfoVO medicineInfoVO = new MedicineInfoVO();
            medicineInfoVO.setId(String.valueOf(documentFields.getSourceAsMap().get("id")));
            medicineInfoVO.setAddress(String.valueOf(documentFields.getSourceAsMap().get("address")));
            medicineInfoVO.setShape(String.valueOf(documentFields.getSourceAsMap().get("shape")));
            medicineInfoVO.setComposition((String.valueOf(documentFields.getSourceAsMap().get("composition"))));
            medicineInfoVO.setTaboo(String.valueOf(documentFields.getSourceAsMap().get("taboo")));
            medicineInfoVO.setNickName(String.valueOf(documentFields.getSourceAsMap().get("nickName")));
            medicineInfoVO.setEffect(String.valueOf(documentFields.getSourceAsMap().get("effect")));
            medicineInfoVO.setFlavorTropism(String.valueOf(documentFields.getSourceAsMap().get("flavorTropism")));
            medicineInfoVO.setName(String.valueOf(documentFields.getSourceAsMap().get("name")));
            medicineInfoVO.setImage(String.valueOf(documentFields.getSourceAsMap().get("image")));
            medicineInfoVO.setGather(String.valueOf(documentFields.getSourceAsMap().get("gather")));
            medicineInfoVO.setType(String.valueOf(documentFields.getSourceAsMap().get("type")));
            medicineInfoVO.setOriginalForm(String.valueOf(documentFields.getSourceAsMap().get("originalForm")));
            medicineInfoVO.setCreateTime(String.valueOf(documentFields.getSourceAsMap().get("createTime")));
            list.add(medicineInfoVO);
        }
    }


    /**
     * 通过id查询
     *
     * @param id
     * @return
     * @throws IOException
     */
    @Override
    public MedicineInfoVO getMedicineDetailById(String id) throws IOException {
        GetRequest getRequest = new GetRequest("medicine", id);
        GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
        sourceAsMap.put("id", getResponse.getId());
        return new ObjectMapper().convertValue(sourceAsMap, MedicineInfoVO.class);
    }

    /**
     * 用户上传草药数据
     *
     * @param medicineCommand
     */
    @Override
    public void uploadMedicine(MedicineCommand medicineCommand) {
        Medicine medicine = new Medicine();
        BeanUtils.copyProperties(medicineCommand, medicine);
        this.save(medicine);
        System.out.println(medicine);
    }

    @Override
    public PageDTO<Medicine> myUploadMedicineList(MyMedicineQuery myMedicineQuery) {
        //获取上下文对象
        SystemLoginUser systemLoginUser = AuthenticationUtils.getSystemLoginUser();
        Assert.isTrue(Objects.nonNull(systemLoginUser), "用户没登陆，没有上下文对象");

        LambdaQueryWrapper<Medicine> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Medicine::getCreatorId, systemLoginUser.getUserId());
        queryWrapper.like(myMedicineQuery.getName() != null, Medicine::getName, myMedicineQuery.getName());
        queryWrapper.eq(myMedicineQuery.getStatus() != null, Medicine::getStatus, myMedicineQuery.getStatus());
        queryWrapper.eq(Medicine::getDeleted, 0);
        queryWrapper.orderByDesc(Medicine::getCreateTime);
        Page<Medicine> page = page(myMedicineQuery.toPage(), queryWrapper);
        //判空，防止空指针异常
        if (CollectionUtils.isEmpty(page.getRecords())) {
            return new PageDTO<Medicine>();
        }
        return new PageDTO<>(page);
    }

    @Override
    public String delMyMedicine(String medicineId) {
        //校验参数
        Assert.isTrue(medicineId != null, "参数为空");
        SystemLoginUser systemLoginUser = AuthenticationUtils.getSystemLoginUser();
        Assert.isTrue(Objects.nonNull(systemLoginUser), "用户未登录");
        //判断草药的状态，草药的发布者，如果草药已经发布就不可以删除，
//        LambdaQueryWrapper<Medicine> queryWrapper = new LambdaQueryWrapper<>();
//        queryWrapper.eq(Medicine::getId,medicineId);
        Medicine medicine = baseMapper.selectById(medicineId);
        if (!medicine.getCreatorId().equals(systemLoginUser.getUserId()) || medicine.getStatus() == 1) {
            return "你发布的草药已经被采纳，快来搜索吧";
        }
        baseMapper.delete(Wrappers.lambdaQuery(Medicine.class).eq(Medicine::getId, medicineId));
        return "成功删除";
    }

    @Override
    public List<UserRanking> rankingUserList() {
        return baseMapper.rankingUserList();
    }

}

